package controller;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.AbstractQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.Metamodel;

import controller.interfaces.IRechnung;
import controller.interfaces.IDatabaseController;

import model.Belegung;
import model.IEntity;
import model.Kunde;
import model.Reservierung;
import model.Zimmer;

/**
 * The controller that manages all persistence features Singleton, for later uses Please make all database queries ONLY in this class !
 * 
 * @author florian, david
 * 
 */
public class PersistenceManager implements IDatabaseController, IRechnung
{
	private static PersistenceManager instance = null;
	private EntityManagerFactory emf;
	private EntityManager em;

	public static PersistenceManager getInstance()
	{
		if (instance == null)
		{
			instance = new PersistenceManager();
		}
		return instance;
	}

	private PersistenceManager()
	{
		init();
		//insertTestData();
	}

	private void init()
	{
		try
		{
			emf = Persistence.createEntityManagerFactory("sese");
			em = emf.createEntityManager();
		} catch (Throwable ex)
		{
			throw new ExceptionInInitializerError(ex);
		}
	}

	private void insertTestData()
	{
		Zimmer z0 = new Zimmer("zimmer0", 1);
		Zimmer z1 = new Zimmer("zimmer1", 2);
		Zimmer z2 = new Zimmer("zimmer2", 3);
		Zimmer z3 = new Zimmer("zimmer3", 3);
		Zimmer z4 = new Zimmer("zimmer4", 3);
		Zimmer z5 = new Zimmer("zimmer4", 3);
		z0.addBelegung(new Belegung());

		this.saveEntity(z0);
		this.saveEntity(z1);
		this.saveEntity(z2);
		this.saveEntity(z3);
		this.saveEntity(z4);
		this.saveEntity(z5);

		try
		{
			Thread.sleep(10000);
		} catch (InterruptedException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		/*
				for (Zimmer z : this.loadAllEntities(new Zimmer()))
					this.deleteEntity(z);
		*/

		this.clearModel();
	}

	public List<Zimmer> getAllZimmer()
	{
		CriteriaBuilder builder = em.getCriteriaBuilder();
		CriteriaQuery<Zimmer> criteria = builder.createQuery(Zimmer.class);
		return em.createQuery(criteria).getResultList();
	}

	public List<Kunde> getAllKunde()
	{
		CriteriaBuilder builder = em.getCriteriaBuilder();
		CriteriaQuery<Kunde> criteria = builder.createQuery(Kunde.class);
		return em.createQuery(criteria).getResultList();
	}

	@Override
	public List<Kunde> searchKunde(String field, String value)
	{

		Query q = em.createQuery("SELECT x FROM Kunde x WHERE UPPER(x." + field + ") LIKE '%" + value.toUpperCase() + "%'");
		return q.getResultList();
	}

	@Override
	public void saveEntity(IEntity entity)
	{
		em.getTransaction().begin();

		em.persist(entity);
		em.getTransaction().commit();
	}

	@Override
	public <T extends IEntity> List<T> loadAllEntities(T e)
	{
		CriteriaBuilder builder = em.getCriteriaBuilder();
		CriteriaQuery<T> criteria = (CriteriaQuery<T>) builder.createQuery(e.getClass());
		return em.createQuery(criteria).getResultList();
	}

	@Override
	public <T extends IEntity> List<T> getAllEntities(Class<T> e)
	{
		CriteriaBuilder builder = em.getCriteriaBuilder();
		CriteriaQuery<T> criteria = (CriteriaQuery<T>) builder.createQuery(e);
		return em.createQuery(criteria).getResultList();
	}

	@Override
	public void deleteEntity(IEntity entity)
	{
		em.getTransaction().begin();
		em.remove(entity);
		em.getTransaction().commit();
	}

	@Override
	public void updateEntity(IEntity entity)
	{
		em.getTransaction().begin();
		em.persist(entity);
		em.getTransaction().commit();
	}

	/**
	 * @param args
	 */
	public static void main(String[] args)
	{
		IDatabaseController pm = PersistenceManager.getInstance();
	}

	@Override
	public Kunde getKundeByName(String vorname, String nachname)
	{
		Kunde u = null;
		Query q = em.createQuery("SELECT u FROM Kunde u WHERE u.vorname = '" + vorname + "' AND u.nachname = '" + nachname + "'");
		List<Kunde> results = q.getResultList();
		if (results.size() >= 1)
		{
			u = results.get(0);
		} else
		{
			u = null;
		}
		return u;
	}

	@Override
	public List<Reservierung> filter(Date von, Date bis, String nachname, String zimmername, boolean onlyUnpayed)
	{
		// where is generated:
		String where = "";
		if (von != null)
			where += "r.von >= :from";
		if (bis != null)
		{
			if (where.length() > 0)
				where += " AND";
			where += " r.bis <= :till";
		}
		if (zimmername != null)
		{
			if (where.length() > 0)
				where += " AND";
			where += " b.zimmer.name = :roomname";
		}
		if (nachname != null)
		{
			if (where.length() > 0)
				where += " AND";
			where += " k.nachname LIKE :surename";
		}
		if (onlyUnpayed)
		{
			if (where.length() > 0)
				where += " AND";
			where += " r.rechnung IS NULL";
		}
		// query is set:
		if(where.length()>0)
			where = " WHERE "+where;
		Query q = em.createQuery("SELECT DISTINCT r FROM Reservierung r JOIN r.belegungen b JOIN b.kunden k" + where);

		// parameters are filled:
		if (von != null)
			q.setParameter("from", von);
		if (bis != null)
			q.setParameter("till", bis);
		if (zimmername != null)
			q.setParameter("roomname", zimmername);
		if (nachname != null)
			q.setParameter("surename", "%"+nachname+"%");

		System.out.println(q.toString());
		List<Reservierung> results = q.getResultList();

		return results;
	}

	/**
	 * Deletes all entities in the DB - use with caution !!!!
	 */
	public void clearModel()
	{

		List<Kunde> kunden = this.getAllEntities(Kunde.class);
		for (Kunde k : kunden)
			this.deleteEntity(k);

		List<Zimmer> zimmer = this.getAllEntities(Zimmer.class);
		for (Zimmer z : zimmer)
			this.deleteEntity(z);

		List<Reservierung> res = this.getAllEntities(Reservierung.class);
		for (Reservierung r : res)
			this.deleteEntity(r);

	}
}
